package com.cc.blog.admin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cc.blog.admin.convert.ArticleDetailConvert;
import com.cc.blog.admin.domain.dto.*;
import com.cc.blog.admin.domain.vo.FindArticleDetailRspVO;
import com.cc.blog.admin.event.DeleteArticleEvent;
import com.cc.blog.admin.event.PublishArticleEvent;
import com.cc.blog.admin.event.UpdateArticleEvent;
import com.cc.blog.admin.mapper.*;
import com.cc.blog.admin.model.*;
import com.cc.blog.admin.service.AdminArticleService;
import com.cc.blog.common.enums.CodeEnum;
import com.cc.blog.common.exception.BusinessException;
import com.cc.blog.common.utils.PageResult;
import com.cc.blog.common.utils.Result;
import com.cc.blog.web.convert.ArticleConvert;
import com.cc.blog.web.domain.dto.UpdateArticleIsTopReqDTO;
import com.cc.blog.web.domain.vo.FindIndexArticlePageListRspVO;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @Author cc
 * @Date 2024/7/11 16:27
 */
@Service
@Slf4j
@AllArgsConstructor
public class AdminArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleDO> implements AdminArticleService {

    private final ArticleContentMapper articleContentMapper;
    private final ArticleCategoryRelMapper articleCategoryRelMapper;
    private final CategoryMapper categoryMapper;
    private final TagMapper tagMapper;
    private final ArticleTagRelMapper articleTagRelMapper;
    private final ApplicationEventPublisher eventPublisher;
    private final ArticleMapper articleMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result publishArticle(PublishArticleReqDTO publishArticleReqDTO) {
        // 1. DTO 转 ArticleDO, 并保存
        ArticleDO articleDO = ArticleDO.builder()
                .title(publishArticleReqDTO.getTitle())
                .cover(publishArticleReqDTO.getCover())
                .summary(publishArticleReqDTO.getSummary())
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .build();
        this.baseMapper.insert(articleDO);
        // 拿到插入记录的主键 ID
        Long articleId = articleDO.getId();
        // 2. DTO 转 ArticleContentDO，并保存
        ArticleContentDO articleContentDO = ArticleContentDO.builder()
                .articleId(articleId)
                .content(publishArticleReqDTO.getContent())
                .build();
        articleContentMapper.insert(articleContentDO);
        // 3. 处理文章关联的分类
        Long categoryId = publishArticleReqDTO.getCategoryId();
        // 3.1 校验提交的分类是否真实存在
        CategoryDO categoryDO = categoryMapper.selectById(categoryId);
        if (Objects.isNull(categoryDO)) {
            log.warn("==> 分类不存在, categoryId: {}", categoryId);
            throw new BusinessException(CodeEnum.CATEGORY_NOT_EXISTED);
        }
        ArticleCategoryRelDO articleCategoryRelDO = ArticleCategoryRelDO.builder()
                .articleId(articleId)
                .categoryId(categoryId)
                .build();
        articleCategoryRelMapper.insert(articleCategoryRelDO);
        // 4. 保存文章关联的标签集合
        List<String> publishTags = publishArticleReqDTO.getTags();
        insertTags(articleId, publishTags);
        // 发送文章发布事件
        eventPublisher.publishEvent(new PublishArticleEvent(this, articleId));
        return Result.success();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result deleteArticle(DeleteArticleReqDTO deleteArticleReqDTO) {
        Long articleId = deleteArticleReqDTO.getId();
        // 1. 删除文章
        this.baseMapper.deleteById(articleId);
        // 2. 删除文章内容
        articleContentMapper.delete(Wrappers.<ArticleContentDO>lambdaQuery()
                .eq(ArticleContentDO::getArticleId, articleId));
        // 3. 删除文章-分类关联记录
        articleCategoryRelMapper.delete(Wrappers.<ArticleCategoryRelDO>lambdaQuery()
                .eq(ArticleCategoryRelDO::getArticleId, articleId));
        // 4. 删除文章-标签关联记录
        articleTagRelMapper.delete(Wrappers.<ArticleTagRelDO>lambdaQuery()
                .eq(ArticleTagRelDO::getArticleId, articleId));
        // 发布文章删除事件
        eventPublisher.publishEvent(new DeleteArticleEvent(this, articleId));
        return Result.success();
    }

    @Override
    public Result findArticlePageList(FindArticlePageListReqDTO findArticlePageListReqDTO) {
        // 分页对象(查询第几页、每页多少数据)
        Page<ArticleDO> page = new Page<>(findArticlePageListReqDTO.getCurrent(), findArticlePageListReqDTO.getSize());
        String title = findArticlePageListReqDTO.getTitle();
        LocalDate startDate = findArticlePageListReqDTO.getStartDate();
        LocalDate endDate = findArticlePageListReqDTO.getEndDate();
        Integer type = findArticlePageListReqDTO.getType();
        // 构建查询条件
        LambdaQueryWrapper<ArticleDO> wrapper = Wrappers.<ArticleDO>lambdaQuery()
                .like(StringUtils.isNotBlank(title), ArticleDO::getTitle, title.trim())
                .ge(Objects.nonNull(startDate), ArticleDO::getCreateTime, startDate)
                .le(Objects.nonNull(endDate), ArticleDO::getCreateTime, endDate)
                .eq(Objects.nonNull(type), ArticleDO::getType, type)
                // 按权重倒序
                .orderByDesc(ArticleDO::getWeight)
                .orderByDesc(ArticleDO::getCreateTime);
        // 执行分页查询
        Page<ArticleDO> articleDOPage = page(page, wrapper);
        List<ArticleDO> articleDOS = articleDOPage.getRecords();
        // DO 转 VO
        List<FindIndexArticlePageListRspVO> vos = null;
        if (!CollectionUtils.isEmpty(articleDOS)) {
            vos = articleDOS.stream()
                    .map(articleDO -> {
                        FindIndexArticlePageListRspVO vo = ArticleConvert.INSTANCE.convertDO2VO(articleDO);
                        // 是否置顶
                        vo.setIsTop(articleDO.getWeight() > 0);
                        return vo;
                    }).collect(Collectors.toList());
        }
        return PageResult.success(articleDOPage, vos);
    }

    @Override
    public Result findArticleDetail(FindArticleDetailReqDTO findArticleDetailReqDTO) {
        Long articleId = findArticleDetailReqDTO.getId();
        ArticleDO articleDO = getById(articleId);
        if (Objects.isNull(articleDO)) {
            log.warn("==> 查询的文章不存在，articleId: {}", articleId);
            throw new BusinessException(CodeEnum.ARTICLE_NOT_FOUND);
        }
        ArticleContentDO articleContentDO = articleContentMapper.selectOne(
                Wrappers.<ArticleContentDO>lambdaQuery().eq(ArticleContentDO::getArticleId, articleId));
        // 所属分类
        ArticleCategoryRelDO articleCategoryRelDO = articleCategoryRelMapper.selectOne(
                Wrappers.<ArticleCategoryRelDO>lambdaQuery().eq(ArticleCategoryRelDO::getArticleId, articleId));
        // 对应标签
        List<ArticleTagRelDO> articleTagRelDOS = articleTagRelMapper.selectList(
                Wrappers.<ArticleTagRelDO>lambdaQuery().eq(ArticleTagRelDO::getArticleId, articleId));
        // 获取对应标签 ID 集合
        List<Long> tagIds = articleTagRelDOS.stream().map(ArticleTagRelDO::getTagId).collect(Collectors.toList());
        // DO 转 VO
        FindArticleDetailRspVO vo = ArticleDetailConvert.INSTANCE.convertDO2VO(articleDO);
        vo.setContent(articleContentDO.getContent());
        vo.setCategoryId(articleCategoryRelDO.getCategoryId());
        vo.setTagIds(tagIds);
        return Result.success(vo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result updateArticle(UpdateArticleReqDTO updateArticleReqDTO) {
        Long articleId = updateArticleReqDTO.getId();
        // 1. VO 转 ArticleDO, 并更新
        ArticleDO articleDO = ArticleDO.builder()
                .id(articleId)
                .title(updateArticleReqDTO.getTitle())
                .cover(updateArticleReqDTO.getCover())
                .summary(updateArticleReqDTO.getSummary())
                .updateTime(LocalDateTime.now())
                .build();
        int count = this.baseMapper.updateById(articleDO);
        // 根据更新是否成功，来判断该文章是否存在
        if (count == 0) {
            log.warn("==> 该文章不存在, articleId: {}", articleId);
            throw new BusinessException(CodeEnum.ARTICLE_NOT_FOUND);
        }
        // 2. DTO 转 ArticleContentDO，并更新
        ArticleContentDO articleContentDO = ArticleContentDO.builder()
                .articleId(articleId)
                .content(updateArticleReqDTO.getContent())
                .build();
        articleContentMapper.update(articleContentDO,
                Wrappers.<ArticleContentDO>lambdaQuery().eq(ArticleContentDO::getArticleId, articleContentDO.getArticleId()));
        // 3. 更新文章分类
        Long categoryId = updateArticleReqDTO.getCategoryId();
        // 3.1 校验提交的分类是否真实存在
        CategoryDO categoryDO = categoryMapper.selectById(categoryId);
        if (Objects.isNull(categoryDO)) {
            log.warn("==> 分类不存在, categoryId: {}", categoryId);
            throw new BusinessException(CodeEnum.CATEGORY_NOT_EXISTED);
        }
        // 先删除该文章关联的分类记录，再插入新的关联关系
        articleCategoryRelMapper.delete(Wrappers.<ArticleCategoryRelDO>lambdaQuery()
                .eq(ArticleCategoryRelDO::getArticleId, articleId));
        ArticleCategoryRelDO articleCategoryRelDO = ArticleCategoryRelDO.builder()
                .articleId(articleId)
                .categoryId(categoryId)
                .build();
        articleCategoryRelMapper.insert(articleCategoryRelDO);
        // 4. 保存文章关联的标签集合
        // 先删除该文章对应的标签
        articleTagRelMapper.delete(Wrappers.<ArticleTagRelDO>lambdaQuery()
                .eq(ArticleTagRelDO::getArticleId, articleId));
        List<String> publishTags = updateArticleReqDTO.getTags();
        insertTags(articleId, publishTags);
        // 发布文章修改事件
        eventPublisher.publishEvent(new UpdateArticleEvent(this, articleId));
        return Result.success();
    }

    @Override
    public Result updateArticleIsTop(UpdateArticleIsTopReqDTO updateArticleIsTopReqDTO) {
        Long articleId = updateArticleIsTopReqDTO.getId();
        Boolean isTop = updateArticleIsTopReqDTO.getIsTop();
        // 默认权重为 0
        Integer weight = 0;
        // 若设置为置顶
        if (Boolean.TRUE.equals(isTop)) {
            // 查询出表中最大的权重值
            ArticleDO articleDO = articleMapper.selectMaxWeight();
            Integer maxWeight = articleDO.getWeight();
            // 最大权重值加一
            weight = maxWeight + 1;
        }
        // 更新该篇文章的权重值
        articleMapper.updateById(ArticleDO.builder()
                .id(articleId)
                .weight(weight)
                .build());
        return Result.success();
    }

    /**
     * 保存标签
     */
    private void insertTags(Long articleId,List<String> publishTags) {
        // 筛选提交的标签（表中不存在的标签）
        List<String> notExistTags = null;
        // 筛选提交的标签（表中已存在的标签）
        List<String> existedTags = null;
        // 查询出所有标签
        List<TagDO> tagDOS = tagMapper.selectList(null);
        // 如果表中还没有添加任何标签
        if (CollectionUtils.isEmpty(tagDOS)) {
            notExistTags = publishTags;
        } else {
            List<String> tagIds = tagDOS.stream().map(tagDO -> String.valueOf(tagDO.getId())).collect(Collectors.toList());
            // 表中已添加相关标签，则需要筛选
            // 通过标签 ID 来筛选，包含对应 ID 则表示提交的标签是表中存在的
            existedTags = publishTags.stream().filter(tagIds::contains).collect(Collectors.toList());
            // 否则则是不存在的
            notExistTags = publishTags.stream().filter(publishTag -> !tagIds.contains(publishTag)).collect(Collectors.toList());

            // 补充逻辑：
            // 还有一种可能：按字符串名称提交上来的标签，也有可能是表中已存在的，比如表中已经有了 Java 标签，用户提交了个 java 小写的标签，需要内部装换为 Java 标签
            Map<String, Long> tagNameIdMap = tagDOS.stream().collect(Collectors.toMap(tagDO -> tagDO.getName().toLowerCase(), TagDO::getId));
            // 使用迭代器进行安全的删除操作
            Iterator<String> iterator = notExistTags.iterator();
            while (iterator.hasNext()) {
                String notExistTag = iterator.next();
                // 转小写, 若 Map 中相同的 key，则表示该新标签是重复标签
                if (tagNameIdMap.containsKey(notExistTag.toLowerCase())) {
                    // 从不存在的标签集合中清除
                    iterator.remove();
                    // 并将对应的 ID 添加到已存在的标签集合
                    existedTags.add(String.valueOf(tagNameIdMap.get(notExistTag.toLowerCase())));
                }
            }
        }
        // 将提交的上来的，已存在于表中的标签，文章-标签关联关系入库
        if (CollectionUtils.isNotEmpty(existedTags)) {
            List<ArticleTagRelDO> articleTagRelDOS = Lists.newArrayList();
            existedTags.forEach(tagId -> {
                ArticleTagRelDO articleTagRelDO = ArticleTagRelDO.builder()
                        .articleId(articleId)
                        .tagId(Long.valueOf(tagId))
                        .build();
                articleTagRelDOS.add(articleTagRelDO);
            });
            // 批量插入
            articleTagRelMapper.insertBatchSomeColumn(articleTagRelDOS);
        }
        // 将提交的上来的，不存在于表中的标签，入库保存
        if (CollectionUtils.isNotEmpty(notExistTags)) {
            // 需要先将标签入库，拿到对应标签 ID 后，再把文章-标签关联关系入库
            List<ArticleTagRelDO> articleTagRelDOS = Lists.newArrayList();
            notExistTags.forEach(tagName -> {
                TagDO tagDO = TagDO.builder()
                        .name(tagName)
                        .createTime(LocalDateTime.now())
                        .updateTime(LocalDateTime.now())
                        .build();
                tagMapper.insert(tagDO);
                // 拿到保存的标签 ID
                Long tagId = tagDO.getId();
                // 文章-标签关联关系
                ArticleTagRelDO articleTagRelDO = ArticleTagRelDO.builder()
                        .articleId(articleId)
                        .tagId(tagId)
                        .build();
                articleTagRelDOS.add(articleTagRelDO);
            });
            // 批量插入
            articleTagRelMapper.insertBatchSomeColumn(articleTagRelDOS);
        }
    }
}
